﻿using System;
using System.Linq;
using System.Xml;
using Mesuro.Common;
using System.Collections.Generic;
using System.IO;
using System.ComponentModel;
using Mesuro.DataManagement;

namespace Mesuro.DataManagement {
	public class Experiment : IXmlSerialisable {
		private ExperimentHeader dbHeader;

		Payload[] payloads;
		public string DataAquisitionSoftware { get; set; }
		public string RecordedBy { get; set; }
		public DateTime Timestamp { get; set; }
		public double Frequency { get; set; }
		public int Harmonics { get; set; }
		public string EquipmentDescription { get; set; }
		/// <summary>
		/// Default constructor. Allows creation of an empty
		/// experiment object which can be used to create new metdata.xml files
		/// </summary>
		public Experiment() { }

		public Experiment( ExperimentHeader dbEntry ) {
			dbHeader = dbEntry;
			try {//FileNotFoundException 

				//Load document into memory
				XmlReader reader = XmlReader.Create( dbEntry.MetadataPath );
				XmlDocument doc = new XmlDocument();
				doc.Load( reader );

				//Process Header
				XmlElement header = (XmlElement)doc.GetElementsByTagName( "mesuro:File-header" ).Item( 0 );

				DataAquisitionSoftware = header.GetElementsByTagName( "mesuro:Data-Acquisation-Software" )[0].InnerText;

				RecordedBy = header.GetElementsByTagName( "mesuro:operator" )[0].InnerText;

				try {
					Timestamp = DateTime.Parse( header.GetElementsByTagName( "mesuro:Timeline" )[0].InnerText );
				} catch (Exception) { }

				try {
					Frequency = Double.Parse( header.GetElementsByTagName( "mesuro:Fundamental-Frequency" )[0].InnerText );
				} catch (Exception) { }

				try {
					Harmonics = Int32.Parse( header.GetElementsByTagName( "mesuro:Num-of-harmonics" )[0].InnerText );
				} catch (Exception) { }

				XmlNodeList payloads = doc.GetElementsByTagName( "mesuro:payload" );

				this.payloads = new Payload[payloads.Count];

				for (int i = 0; i < payloads.Count; i++) {
					this.payloads[i] = new Payload( (XmlElement)payloads[i] );
				}

			} catch (FileNotFoundException ex) {
				throw;
			}
		}

		private DatabaseSeries _series = null;
		public ISeries Series {
			get {
				if (_series == null) {
					LoadAllPayloads();
					_series = new DatabaseSeries( dbHeader.ShortDescription, Frequency, Harmonics );
					int nPoints = 0;
					foreach (Payload p in payloads)
						nPoints = Math.Max( nPoints, p.DataPoints );

					foreach (Payload p in payloads)
						_series.AddPayload( p, nPoints / p.DataPoints );
				}
				return _series;
			}
		}

		public XmlElement XMLSerialise( XmlDocument doc ) {
			XmlElement metadata = doc.CreateElement( "mesuro:metadata" );
			//Add File-Header element
			//add all the fields in File-Header element (operator, Timestamp,frequency etc.)
			foreach (Payload pl in payloads) {
				metadata.AppendChild( pl.XMLSerialise( doc ) );
			}
			return metadata;
		}

		public void LoadAllPayloads() {
			foreach (Payload pl in payloads)
				pl.ReadCSV();
		}
	}
	internal class DatabaseSeries : ISeries {

		private readonly Dictionary<Payload, int> payloads = new Dictionary<Payload, int>();

		public double Frequency { get; set; }
		public int Harmonics { get; set; }

		public void AddPayload( Payload payload, int repeat ) {
			payloads.Add( payload, repeat );

			foreach (IChannel c in payload.Channels)
				_channels.AddChannel( c );
		}

		private readonly DefaultChannelCollection _channels = new DefaultChannelCollection();
		public IChannelCollection Channels {
			get { return _channels; }
		}

		public string Name { get; set; }

		public DatabaseSeries( string name, double frequency, int harmonics ) {
			Name = name;
			Frequency = frequency;
			Harmonics = harmonics;
			_channels.AddChannel( new DefaultImmutableChannel( "freq", new Limits( frequency, frequency * harmonics ) ) );
		}

		public int Dimensions {
			get { return Channels.Count; }
		}

		public event NotifySeriesChangedEventHandler DataChanged;

		public IEnumerator<Vector> GetEnumerator() {
			int i = 0;
			IEnumerator<Vector>[] sources = new IEnumerator<Vector>[payloads.Count];
			int[] indices = new int[payloads.Count];
			foreach (KeyValuePair<Payload, int> p in payloads) {
				sources[i] = p.Key.GetEnumerator();
				indices[i] = p.Value;
				i++;
			}

			i = 0;
			while (true) {
				for (int j = 0; j < sources.Length; j++)
					if (i % indices[j] == 0)
						if (!sources[j].MoveNext()) yield break;
				Vector ret = new Vector( Frequency * (i % Harmonics) );
				foreach (IEnumerator<Vector> e in sources)
					ret = Vector.Concatenate( ret, e.Current );
				yield return ret;
				i++;
			}
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
			return GetEnumerator();
		}
	}
}

/// <summary>
/// Cycling channel provides an optimised method of storing cyclic _data in a channel format. 
/// For example, frequency of a harmonic signal which contains only the values 9GHz, 18GHz, 27GHz and then repeats.
//    /// </summary>
//    public class CyclingChannel : IChannel {
//        /// <summary>
//        /// Backing variable to store the _data.
//        /// </summary>
//        private double[] _data;
//        /// <summary>
//        /// Gets the i'th _data point. This method will always return a value even if <code>i > Length</code>.
//        /// </summary>
//        /// <param seriesKey="index">the index of the datapoint to return</param>
//        /// <returns>A _data point</returns>
//        public double this[int index] {
//            get { return _data[index % _data.Length]; }
//        }

//        /// <summary>
//        /// The number of _data points in this channel. This value can be adjusted by using <see cref="SetCycles"/>.
//        /// </summary>
//        public int Length { get; private set; }

//        /// <summary>
//        /// The seriesKey of this channel. For example, "frequency"
//        /// </summary>
//        public string Name {
//            get;
//            set;
//        }

//        /// <summary>
//        /// Backing variable to store the limits of this channel.
//        /// </summary>
//        private Limits _limits;
//        /// <summary>
//        /// Gets the Limits of the channel.
//        /// </summary>
//        public Limits Limits {
//            get { return _limits; }
//        }

//        /// <summary>
//        /// Constructs a cycling channel. All _data must be supplied to the constructor in order.
//        /// </summary>
//        /// <param seriesKey="_data"></param>
//        public CyclingChannel( params double[] data ) {
//            this._data = data;
//            this.Length = data.Length;
//            //TODO: Calculate limits of _data and set _limits.
//        }

//        /// <summary>
//        /// Set the length reported by the channel.
//        /// </summary>
//        /// <param seriesKey="nCycles">how many cycles should the channel report to have</param>
//        public void SetCycles( int nCycles ) {
//            Length = _data.Length * nCycles;
//        }

//    }
//}